/*
Abstract:
  Assembler serializing functionality implementation

Last changed:
  $Id: output_asm.cpp 82 2009-12-08 20:53:44Z vitamin.caig $

Author:
  (C) Vitamin/CAIG/2001

  This file is the part of Textator project
*/
#include "outputter.h"
#include "header_footer_supp.h"

#include "../io/streams.h"

#include <symbol_check.h>

#include <cassert>
#include <limits>

#include <text.h>

namespace
{
  const String::value_type DEFTOKEN_MACRO[] = {'D', 'E', 'F', 'T', 'O', 'K', 'E', 'N', ' ', '\0'};
  const String::value_type TOKEN_MACRO[] = {'T', 'O', 'K', 'E', 'N', '\0'};
  const String EMPTY;

  const std::size_t MINIMAL_WIDTH = 32;

  inline bool IsAsmEscaped(const String::value_type sym)
  {
    return sym == '\'' || sym == '\"' || sym == '\\';
  }

  class AssemblerOut : public Output::Outputter
  {
  public:
    explicit AssemblerOut(const Output::AssemblerParameters& ap) : Params(ap),
      TabPad(Params.Tab, ' ')
    {
      Params.Width = std::max(Params.Width, MINIMAL_WIDTH);
    }

    virtual void Process(const EntriesMap& input, IO::TargetStream& stream) const
    {
      OStringStream localstream, globalstream;
      localstream << TEXT_LOCAL_ENTRIES_ASM;
      globalstream << TEXT_GLOBAL_ENTRIES_ASM;
      Output::HeaderFooterController::Ptr hfctrl(Output::CreatePerLineController(TEXT_HEADER_FORMAT_ASM));
      for (EntriesMap::const_iterator it = input.begin(), lim = input.end(); it != lim; ++it)
      {
        if (it->second.Value.empty() || !hfctrl->ParseEntry(it->first, it->second.Value.front().Value))
        {
          if (Entry::LOCAL == it->second.Type)
          {
            localstream << String(Params.Tab, ' ') << DEFTOKEN_MACRO << it->first << '\n';
            PutText(EMPTY, it->second.Value, localstream);
          }
          else if (Entry::GLOBAL == it->second.Type)
          {
            //check for simple alias
            const String& label(it->first);
            const TokensList& tkns(it->second.Value);
            if (!Params.Inline &&
              1 == tkns.size() &&
              Token::VARIABLE == tkns.front().Type)
            {
              //alias may be only for globals (they're guaranteed to be texts)
              //not supported in inline mode
              EntriesMap::const_iterator refIt(input.find(tkns.front().Value));
              if (refIt != input.end() &&
                Entry::GLOBAL == refIt->second.Type)
              {
                globalstream << label << ' ' << Params.Equ << ' ' << refIt->first << '\n';
                continue;
              }
            }
            if (Params.Inline)
            {
              globalstream << TabPad << Params.Macro << ' ' << label << '\n';
              PutText(EMPTY, tkns, globalstream);
              globalstream << TabPad << Params.EndMacro << '\n' << '\n';
            }
            else
            {
              PutText(label + ':', tkns, globalstream);
            }
          }
          else
          {
            assert(!"Invalid type");
          }
        }
      }

      hfctrl->PutHeader(stream);
      stream.PutString(localstream.str());
      stream.PutString(globalstream.str());
      hfctrl->PutFooter(stream);
    }

    virtual void Process(const StringMap& input, IO::TargetStream& stream) const
    {
      OStringStream str;
      Output::HeaderFooterController::Ptr hfctrl(Output::CreatePerLineController(TEXT_HEADER_FORMAT_ASM));
      for (StringMap::const_iterator it = input.begin(), lim = input.end(); it != lim; ++it)
      {
        if (!hfctrl->ParseEntry(it->first, it->second))
        {
          PutText(it->first + ':', it->second, true, str);
        }
      }
      hfctrl->PutHeader(stream);
      stream.PutString(str.str());
      hfctrl->PutFooter(stream);
    }
  private:
    void OutLabel(const String& label, OutStream& stream) const
    {
      if (label.size() < Params.Tab - 2)
      {
        stream << label << String(Params.Tab - label.size(), ' ');
      }
      else
      {
        stream << label << '\n' << TabPad;
      }
    }

    void PutText(const String& label, const TokensList& tkns, OutStream& str) const
    {
      //check for empty
      if (tkns.empty())
      {
        return PutText(label, EMPTY, true, str);
      }
      for (TokensList::const_iterator it = tkns.begin(), lim = tkns.end(); it != lim;)
      {
        const String& lbl(it == tkns.begin() ? label : EMPTY);
        const Token& tkn(*it);
        bool finalize(++it == lim);
        if (Token::TEXT == tkn.Type)
        {
          PutText(lbl, tkn.Value, finalize, str);
        }
        else if (Token::VARIABLE == tkn.Type)
        {
          OutLabel(lbl, str);
          str << TOKEN_MACRO << ' ' << tkn.Value << '\n';
          if (finalize)
          {
            PutText(EMPTY, EMPTY, true, str);
          }
        }
        else if (Token::BINARY == tkn.Type)
        {
          PutText(lbl, tkn.Value, false, str, true);
        }
        else
        {
          assert(!"Invalid token type");
        }
      }
    }

    class StrContext : public std::unary_function<String::value_type, void>
    {
      StrContext(const StrContext&);
      const StrContext& operator = (const StrContext&);
    public:
      StrContext(const String& label, OutStream& str, const Output::AssemblerParameters& par, bool finalize, bool asBinary = false) :
        PrevMode(MARGIN), FreeSyms(false), DbPad(par.Tab + par.DefB.size() + 1, ' '),
        Stream(str), Params(par), Finalize(finalize), AsBinary(asBinary)
      {
        std::copy(Params.DefB.begin(), Params.DefB.end(), DbPad.begin() + Params.Tab);
        Stream << label;
        if (label.size() < Params.Tab - 1)
        {
          Stream << DbPad.substr(label.size());
        }
        else
        {
          Stream << '\n' << DbPad;
        }
        FreeSyms = Params.Width - DbPad.size();
      }
      ~StrContext()
      {
        if (Finalize)
        {
          operator()(0);
        }
        else if (TEXT == PrevMode)
        {
          Stream << '\"';
        }
        Stream << '\n';
      }

      result_type operator () (argument_type sym)
      {
        /* in common case:
        From| marg | text | symb
        To \|      |      |
        ----+------+------+-----
        marg| none |  "   |
        text|  "   | none | ,"
        symb| none |  ",  | ,
        */
        //output delimiter + data
        Mode thisMode(AsBinary || Symbol::IsEscaped(sym) || IsAsmEscaped(sym) ? DATA : TEXT);

        //check for margin (maximal mode)
        //sym->text: 4 (,"?")
        //sym->sym: 1+digits (,?)
        //text->text: 2 (?")
        //text->sym: 2 + digits (",?)
        std::size_t required(TEXT == thisMode ? 4 : 2 + std::numeric_limits<String::value_type>::digits10);
        if (required > FreeSyms)
        {
          if (TEXT == PrevMode)//finalize string
          {
            Stream << '\"';
          }
          ResetString();
          return operator()(sym);//onse more
        }

        if (thisMode != PrevMode)
        {
          if (MARGIN == PrevMode)//from begin
          {
            if (TEXT == thisMode)//prepare text
            {
              Stream << '\"';
              --FreeSyms;
            }
          }
          else
          {
            //sym->text:  ,"
            if (TEXT == thisMode)
            {
              Stream << ',' << '\"';
            }
            else //text->sym: ",
            {
              Stream << '\"' << ',';
            }
            FreeSyms -= 2;
          }
        }
        else if (DATA == thisMode)
        {
          Stream << ',';
          --FreeSyms;
        }
        if (TEXT == thisMode)
        {
          Stream << sym;
          --FreeSyms;
        }
        else
        {
          const String& val(ToString<String>::Convert(static_cast<size_t>(sym)));
          Stream << val;
          FreeSyms -= val.size();
        }
        PrevMode = thisMode;
      }
    private:
      void ResetString()
      {
        Stream << '\n' << DbPad;
        FreeSyms = Params.Width - DbPad.size();
        PrevMode = MARGIN; //reset
      }
    private:
      enum Mode
      {
        MARGIN,
        TEXT,
        DATA
      };
      Mode PrevMode;
      std::size_t FreeSyms;
    private:
      String DbPad;
      OutStream& Stream;
      const Output::AssemblerParameters& Params;
      bool Finalize;
      const bool AsBinary;
    };

    void PutText(const String& label, const String& value, bool finalize, OutStream& str, bool asBinary = false) const
    {
      StrContext ctx(label, str, Params, finalize, asBinary);
      std::for_each(value.begin(), value.end(), RefWrap(ctx));//to prevent object copy
    }
  private:
    Output::AssemblerParameters Params;
    String TabPad;
  };
}

namespace Output
{
  Outputter::Ptr CreateAssemblerOutputter(const AssemblerParameters& params)
  {
    return Outputter::Ptr(new AssemblerOut(params));
  }
}
